package com.basicorm;

import com.basicorm.annotations.Column;
import com.basicorm.annotations.Table;
import com.basicorm.utils.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

public class EntityStorageImplEx<T> {
	private final Connection connection;
	private final Class<T> classActual;

	public EntityStorageImplEx(Connection connection, Class<T> classActual) {
		this.connection = connection;
		this.classActual = classActual;
	}

	public Collection<T> findByExample(T example) throws Throwable {
		if (example == null){
			return findAll();
		}
        // 1. get Company(s) by example from person.company
//        2. use company ids as additional conditional to find person objects

//        3. each person gets company with only one field set - company id
//        4. for each person find company by example and set it to person
//

		//return selectFromTableCondition(getTableName(), getConditionString(example));
		return selectFromTableCondition(getTableName(), getTableInf(example));
	}


	private Collection<T> selectFromTableCondition(String tableName, Map<String, Object> map2) throws SecurityException, Throwable {
		List<String> columns = getColumns();
		List<Map<String, Object>> columnNameValue = selectValuesConditionFromMap(tableName, columns, map2);
		Map<String, Field> mapColumnNameField = getColumnsFieldInf(classActual);
		Collection<T> resultList = new LinkedList<T>();
		for (Map<String, Object> map: columnNameValue){
			T instance = createInstance();
			copyValuesToObject(map, mapColumnNameField, instance);
			resultList.add(instance);
		}
		return resultList;
	}

	private List<Map<String, Object>> selectValuesCondition(String tableName,
			List<String> columnNames, String conditionString) throws Throwable, SecurityException, IllegalArgumentException, IllegalAccessException {
		String query = "select * from " + tableName + " where " + conditionString;
		ResultSet result = connection.createStatement().executeQuery(query);
		List<Map<String, Object>> columnValues = new ArrayList<Map<String, Object>>();
		while (result.next()) {
			columnValues.add(inMap(result, columnNames));
		}
		return columnValues;
	}
	
	List<Map<String, Object>> selectValuesConditionFromMap(String tableName,
			List<String> columnNames, Map<String, Object> conditionMap) throws Throwable, SecurityException, IllegalArgumentException, IllegalAccessException {
		Set<Map.Entry<String, Object>> set = conditionMap.entrySet();
		List<Map<String, Object>> columnValues = new ArrayList<Map<String, Object>>();
		String query = "select * from " + tableName + " where " + makeConditionWithQueryMark(conditionMap);
		PreparedStatement statement = connection.prepareStatement(query);
		statement = insertColumnValue(statement, set);
		ResultSet result = statement.executeQuery();
		while (result.next()) {
			columnValues.add(inMap(result, columnNames));
		}
		return columnValues;
	}
	

	private PreparedStatement insertColumnValue(PreparedStatement statement,
			Set<Entry<String, Object>> set) throws SQLException {
		int i = 1;
		for (Map.Entry<String, Object> setFromMap: set){
			statement.setObject(i, setFromMap.getValue());
			i++;
		}
		return statement;		
	}

	private String makeConditionWithQueryMark(Map<String, Object> conditionMap) {
		Set<Map.Entry<String, Object>> set = conditionMap.entrySet();
		Collection<String> collection = new LinkedList<String>();
		for (Map.Entry<String, Object> i: set){
			if (i.getValue() != null){
				collection.add(i.getKey() + "= ?");
			}
		}
		return StringUtils.join(" and ", collection);
	}

	private String getConditionString(T example) throws SQLException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		Map<String, Object> map = getTableInf(example);
		Set<Map.Entry<String, Object>> set = map.entrySet();
		Collection<String> collection = new LinkedList<String>();
		for (Map.Entry<String, Object> i: set){
			if (i.getValue() != null){
				collection.add(i.getKey() + "=" + i.getValue().toString());
				//conditionString = " and " + i.getKey() + "=" + i.getValue().toString();	
			}
		}
		return StringUtils.join(" and ", collection);
	}
	
//	private Map<String, Object> getConditionMap(T example) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
//		Map<String, Object> map = getTableInf(example);
//		return map;
//	}

	public void insert(T entity) throws SQLException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		connection.createStatement().executeUpdate(getSql(entity));
	}

	private String getSql(T entity) throws NoSuchFieldException, SecurityException, IllegalAccessException {
		Map<String, Object> map = getTableInf(entity);
		String columns = getColumnName(map);
		String content = getContents(map);
		String sql = "insert into " + getTableName() + "("+ columns +") values ("+ content +")";
		//printField1(sql);
		return sql;
	}

	private String getContents(Map<String, Object> map) {
		Collection<String> collection = new ArrayList<String>();
		Set<Map.Entry<String, Object>> set = map.entrySet();
		for (Map.Entry<String, Object> i: set){
			collection.add(i.getValue().toString());
		}
		//printField1(set.toString());
		//collection = StringUtils.quote(collection);
		collection = StringUtils.quote(collection);
		String content = StringUtils.join(",", collection);
		return content;
	}

	private String getColumnName(Map<String, Object> map) {
		Collection<String> collection = new ArrayList<String>();
		Set<Map.Entry<String, Object>> set = map.entrySet();
		for (Map.Entry<String, Object> i: set){
			collection.add(i.getKey());
		}
		String columns = StringUtils.join(",", collection);
		return columns;
	}

	public Map<String, Object> getTableInf(T entity) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		Map<String, Object> map = new TreeMap<String, Object>();
		for (Field f: classActual.getDeclaredFields()){
			//printField(f, entity);
			if (f.getAnnotation(Column.class) != null){
				//
				f.setAccessible(true);
				//
				if (f.get(entity) != null){
					map.put(f.getAnnotation(Column.class).value(), f.get(entity));
				}
			}
		}

		return map;
	}


	private void printField1(String columns){
		System.out.println("columns "+ columns);
	}

	private void printField(Field f, T entity) throws IllegalArgumentException, IllegalAccessException {
		System.out.println(f.get(entity));
		System.out.println(f);
		System.out.println(f.getName());
		System.out.println(f.isAccessible());
		for (Annotation o: f.getAnnotations()){
			System.out.println(o);
		}
	}

	private String getTableName() {
		return classActual.getAnnotation(Table.class).value();
	}

	public Collection<T> findAll() throws SecurityException, Throwable {
		Map mapTmp = new HashMap();
		mapTmp.put("1", "1");
		return selectFromTableCondition(getTableName(), mapTmp); 
	}


	public List<String> getColumns() {
		List<String> listColumn = new ArrayList<String>();
		for (Field f: classActual.getDeclaredFields()){
			if (f.getAnnotation(Column.class) != null){
				listColumn.add(f.getAnnotation(Column.class).value());
			}
		}
		return listColumn;
	}


	//TODO List<Map<String, string>> selectValues(String tableName, List columnNames){
	// Map<columnName, columnValue>} 
	//TODO method2(Map<String, string> values, Map<String columnName, Field field>, T object){
	//take values from Map1 and sets to fields t} copy values of to object
	//TODO Map<String columnName, Field field> getColumnsFieldInf(Class c){}

	List<Map<String, Object>> selectValues(String tableName, List<String> columnNames) throws SQLException{
		ResultSet result = connection.createStatement().executeQuery("select * from " + tableName);
		List<Map<String, Object>> columnValues = new ArrayList<Map<String, Object>>();
		while (result.next()) {
			columnValues.add(inMap(result, columnNames));
		}
		return columnValues;
	}

	private Map<String, Object> inMap(ResultSet result, List<String> columnNames) throws SQLException {
		Map<String, Object> columnValue = new HashMap<String, Object>();
		for (String f: columnNames){
			columnValue.put(f, result.getObject(f));
		}
		return columnValue;
	}

	void copyValuesToObject(Map<String, Object> values, Map<String, Field> columnNameFeild, T object) throws IllegalArgumentException, IllegalAccessException{
		for (Map.Entry<String, Object> value: values.entrySet()){
			columnNameFeild.get(value.getKey()).set(object, value.getValue());
		}
	}

	Map<String, Field> getColumnsFieldInf(Class<T> c){
		Map<String, Field> result = new HashMap<String, Field>();
		for (Field f: c.getDeclaredFields()){
			if (f.getAnnotation(Column.class) != null){
				f.setAccessible(true);
				result.put(f.getAnnotation(Column.class).value(), f);
			}
		}
		return result;
	}



	private T createInstance() {
		try {
			return classActual.getConstructor().newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
